'use strict' import { NAMESPACE_SEPARATOR } from '../../../../../redux-scuttlebutt/lib/constants' import { resolve } from 'url' import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api' import { BuildStatus, BuildResult, Build } from 'azure-devops-node-api/interfaces/BuildInterfaces' import { WrongInputError, NotFoundError, InternalError } from '../../../utils/customErrors' import { promisifyAll } from 'bluebird' const fs = promisifyAll(require('fs')) import { getBotById } from '../storageService' import RealtimeService from '../../../../realtimeService' import { IBuildApi } from 'azure-devops-node-api/BuildApi' import { Bot } from '../../../../../schema' import { Request } from 'express' const INTERVAL = 3000 const PIPELINE = 8 let runningBuilds = new Map() let buildApi: IBuildApi if (process.env.DEVOPS_ORG_URL && process.env.DEVOPS_PAT && process.env.DEVOPS_PROJECT) { const authHandler = getPersonalAccessTokenHandler(process.env.DEVOPS_PAT) const connection = new WebApi(process.env.DEVOPS_ORG_URL, authHandler) connection .getBuildApi() .then(async _buildApi => { if (!_buildApi) { console.error('CANNOT GET BUILD API') return } buildApi = _buildApi const builds = await _getBuildsInProgress() //Build.id and Build.parameters builds.forEach(({ id, parameters }) => { const botId = JSON.parse(parameters)['feedbot.id'] runningBuilds.set(botId, id) }) setInterval(_checkBuildStatuses, INTERVAL) }) .catch(error => { console.error('CANNOT GET BUILD API', error) }) } async function _checkBuildStatuses() { const buildIds = [...runningBuilds.values()] if (buildIds.length) { const builds = await _getBuildsById(buildIds) builds.forEach(({ parameters, status, result }) => { if (result === BuildResult.Failed || result === BuildResult.Canceled) { let botId = JSON.parse(parameters)['feedbot.id'] runningBuilds.delete(botId) botId = `${process.env.REALTIME_NAMESPACE}${NAMESPACE_SEPARATOR}${botId}` RealtimeService.dispatch(botId, 'CREATE_HOSTING_FAILURE', { error: 'There was a problem with creating new hosting.' }) } }) } } function _getBuildsInProgress() { return _getBuilds() } function _getBuildsById(buildIds: number[]) { return _getBuilds(buildIds) } function _getBuilds(buildIds?: number[]) { return ( buildApi && buildApi.getBuilds( process.env.DEVOPS_PROJECT, buildIds ? null : [8], null, null, null, null, null, null, buildIds ? null : BuildStatus.InProgress, null, null, null, null, null, null, null, null, null, buildIds || null ) ) } async function _checkBot(botId: string) { const bot = await getBotById(botId) if (!bot) { throw 'Invalid bot id.' } else if (bot.bot.apiUrl || bot.bot.directLineSecret || bot.bot.previewUrl) { throw 'Hosting is already created.' } } function _checkHostingProgress(botId: string) { if (runningBuilds.has(botId)) { throw 'Hosting is already being created.' } } async function _createHosting(botId: string, originUrl: string) { if (buildApi) { const parameters: { [x: string]: string } = { 'feedbot.id': botId, 'feedbot.name': botId, 'feedbot.originUrl': originUrl } if (process.env.SERVER_URL) { parameters['feedbot.callbackUrl'] = resolve( process.env.SERVER_URL, `/api/bots/${botId}/hosting` ) } return await buildApi.queueBuild( { definition: { id: PIPELINE }, parameters: JSON.stringify(parameters) }, process.env.DEVOPS_PROJECT ) } } function _saveBot(bot: Bot) { const file = `bots/${bot.id}.${bot.version}.json` return fs.writeFileAsync(file, JSON.stringify(bot)) } export default { async postHosting(req: Request) { const botId = req.params.id const { originUrl } = req.body if (botId) { try { await _checkBot(botId) _checkHostingProgress(botId) const build = await _createHosting(botId, originUrl) if (build && build.id) { runningBuilds.set(botId, build.id) return } else { throw new InternalError('build.id is undefined because hosting was not created.') } } catch (error) { console.error(error) throw new InternalError(error) } } else { throw new WrongInputError('bot id is undefined') } }, async patchHosting(req: Request) { const token = req.get('Authorization') if (token && token === `Bearer ${process.env.DEVOPS_PAT}`) { try { let botId = req.params.id const { apiUrl, directLineSecret, previewUrl } = req.query const bot = await getBotById(botId) await _saveBot({ ...bot.bot, apiUrl, directLineSecret, previewUrl }) runningBuilds.delete(botId) botId = `${process.env.REALTIME_NAMESPACE}${NAMESPACE_SEPARATOR}${botId}` RealtimeService.dispatch(botId, 'CREATE_HOSTING_SUCCESS', { payload: { apiUrl, directLineSecret, previewUrl } }) return } catch (error) { console.error(error) throw new InternalError(error) } } else { throw new WrongInputError('Invalid token') } } }